package com.soccertgc.core.game
{
	
	import com.soccertgc.core.DataObject;
	import com.soccertgc.core.DispatcherDataObject;
	import com.soccertgc.core.Player;
	import com.soccertgc.core.Position;
	import com.soccertgc.core.Role;
	import com.soccertgc.core.User;
	import com.soccertgc.core.cards.CardBallCondition;
	import com.soccertgc.core.cards.Deck;
	import com.soccertgc.core.game.events.GameEvent;
	import com.soccertgc.core.game.events.GameStatusChangedEvent;
	import com.soccertgc.logging.Dumper;
	import com.soccertgc.logging.IDumpable;
	import com.soccertgc.logging.Logger;
	
	public class Game extends DispatcherDataObject implements IDumpable
	{

		// -------------------------------------------------------------------------------
		// CONSTANTS
		// -------------------------------------------------------------------------------
		
		// Logging
		public static const			LOG_MARKER:String						= "GAME > ";
		public static const			DEBUG_KEY:String						= "core.game";
		
		// Invalid turn index
		public static const			INVALID_TURN:uint						= 0;
		
		// JSON properties names
		protected static const		JSON_PROPERTY_ID:String               	= "gameId";
		protected static const		JSON_PROPERTY_STATUS:String             = "status";
		protected static const		JSON_PROPERTY_TURN:String               = "currentTurn";
		protected static const		JSON_PROPERTY_SIDE:String               = "userSide";
		protected static const		JSON_PROPERTY_HOME:String               = "home";
		protected static const		JSON_PROPERTY_AWAY:String               = "away";
		
		// Default values
		public static const         DEFAULT_ID:int							= DataObject.INVALID_ID;
		public static const         DEFAULT_SIDE:GameSide					= GameSide.NONE;
		public static const         DEFAULT_TURN:int						= INVALID_TURN;
		public static const         DEFAULT_STATUS:GameStatus				= GameStatus.UNKNOWN;
		
		// -------------------------------------------------------------------------------
		// FIELDS
		// -------------------------------------------------------------------------------
		
		private var _id:uint;
		private var _status:GameStatus;
		private var _options:GameOptions;
		private var _player:User;
		private var _opponent:Player;
		private var _deck:GameDeck; // Riferimento al deck con cui si sta giocando la partita
		private var _ball:GameBall;
		private var _side:GameSide;
		private var _home:GameTeam;
		private var _away:GameTeam;
		private var _turn:int;
		private var _passedPhases:int; // Number of passed phases
		private var _turns:GameTurns;
		
		// -------------------------------------------------------------------------------
		// CONSTRUCTOR
		// -------------------------------------------------------------------------------
		
		public function Game(player:User, options:GameOptions, gameDeck:GameDeck = null, deck:Deck = null)
		{
			super();
			// Options
			_options = options;
			// Init player
			_player = player;
			// Init deck
			if (gameDeck != null)
			{
				_deck = gameDeck;
			}
			else
			{
				_deck = new GameDeck(deck);
			}
			
			// Init object
			_ball = new GameBall();
			_home = new GameTeam(this);
			_away = new GameTeam(this);
			_turns = new GameTurns();
			// Init data
			clear();
		}
		
		// -------------------------------------------------------------------------------
		// PROPERTIES
		// -------------------------------------------------------------------------------

		[Bindable(event="gameChanged")]
		public function get id():uint
		{
			return _id;
		}
		
		// LT_TODO: to remove
		public function set id(value:uint):void
		{
			_id = value;
		}
		
		[Bindable(event="gameChanged")]
		public function get status():GameStatus
		{
			return _status;
		}
		
		public function get options():GameOptions
		{
			return _options;
		}
		
		public function set options(value:GameOptions):void
		{
			_options = value;
		}
		
		[Bindable(event="gameChanged")]
		public function get name():String
		{
			return isValid	? _home.name + " vs " + _away.name
							: "Game unknown";
		}
		
		[Bindable(event="gameChanged")]
		public function get score():String
		{
			return isValid	? _home.score + " - " + _away.score
							: "0 - 0";
		}
		
		[Bindable(event="gameChanged")]
		public function get turn():int
		{
			return _turn;
		}
		
		[Bindable(event="gameChanged")]
		public function get passedPhases():int
		{
			return _passedPhases;
		}
		
		[Bindable(event="gameChanged")]
		public function get player():User
		{
			return _player;
		}
		
		[Bindable(event="gameChanged")]
		public function get opponent():Player
		{
			return _opponent;
		}
		
		// LT_TODO: to remove DEBUG ONLY
		public function set opponent(value:Player):void
		{
			_opponent = value;
		}
		
		[Bindable(event="gameChanged")]
		public function get ball():GameBall
		{
			return _ball;
		}
		
		[Bindable(event="gameChanged")]
		public function get side():GameSide
		{
			return _side;
		}
		
		[Bindable(event="gameChanged")]
		public function get playerTeam():GameTeam
		{
			if (_side == GameSide.HOME) return _home;
			return _away;
		}
		
		[Bindable(event="gameChanged")]
		public function get opponentTeam():GameTeam
		{
			if (_side == GameSide.HOME) return _away;
			return _home;
		}
		
		[Bindable(event="gameChanged")]
		public function get playerActivePlayerCard():GamePlayerCard
		{
			var t:GameTeam = playerTeam;
			return t.getPlayer(t.isAttacking ? _ball.position : _ball.oppositePosition);
		}
		
		[Bindable(event="gameChanged")]
		public function get opponentActivePlayerCard():GamePlayerCard
		{
			var t:GameTeam = opponentTeam;
			return t.getPlayer(t.isAttacking ? _ball.position : _ball.oppositePosition);
		}
		
		[Bindable(event="gameChanged")]
		public function get playerActivePosition():Position
		{
			return playerTeam.isAttacking ? _ball.position : _ball.oppositePosition; 
		}
		
		[Bindable(event="gameChanged")]
		public function get opponentActivePosition():Position
		{
			return playerTeam.isAttacking ? _ball.oppositePosition : _ball.position;
		}

		[Bindable(event="gameChanged")]
		public function get attackingTeam():GameTeam
		{
			return playerTeam.isAttacking ? playerTeam : opponentTeam;
		}
		
		[Bindable(event="gameChanged")]
		public function get defendingTeam():GameTeam
		{
			return playerTeam.isAttacking ? opponentTeam : playerTeam;
		}
		
		[Bindable(event="gameChanged")]
		public function get isValid():Boolean
		{
			return	_side != GameSide.NONE	&&
					_home.isValid 			&&
					_away.isValid;
		}
		
		public function get deck():GameDeck
		{
			return _deck;
		}
		
		public function get isStarted():Boolean
		{
			return _turn > INVALID_TURN;
		}
		
		public function get isInProgress():Boolean
		{
			return	_status	==	GameStatus.SUBSTITUTION				||
					_status	==	GameStatus.RESOLVE_SUBSTITUTION		||
					_status	==	GameStatus.DISCARD					||
					_status	==	GameStatus.RESOLVE_DISCARD			||
					_status	==	GameStatus.PLAY						||
					_status	==	GameStatus.RESOLVE_PLAY 			||
					_status	==	GameStatus.RESYNC;
		}
		
		public function get isEnded():Boolean
		{
			return _status == GameStatus.DONE;
		}
		
		public function get isInDesync():Boolean
		{
			return _status == GameStatus.DESYNC;
		}
		
		public function get isInError():Boolean
		{
			return _status == GameStatus.ERROR;
		}

		public function get isFirstTurn():Boolean
		{
			return _turn == 1;
		}
		
		public function get isLastTurn():Boolean
		{
			return _turn == _options.turns;
		}
		
		public function get isFirstTurnForPeriod():Boolean
		{
			return	_turn == _options.firstTurnOfFirstPeriod || 
					_turn == _options.firstTurnOfSecondPeriod;
		}
		
		public function get isInFirstPeriod():Boolean
		{
			return	_turn != INVALID_TURN && _options.turnsForPeriod;
		}
		
		public function get isInSecondPeriod():Boolean
		{
			return _turn != INVALID_TURN && _turn > _options.turnsForPeriod;
		}
		
		public function get isDiscardToDo():Boolean
		{
			return !isFirstTurnForPeriod && isInFirstPeriod;
		}
		
		public function get isHalfTime():Boolean
		{
			return isInSecondPeriod && isFirstTurnForPeriod;
		}
		
		public function get isPlayerAttacking():Boolean
		{
			return playerTeam.role == GameRole.ATTACK;
		}
		
		public function get isPlayerDefending():Boolean
		{
			return playerTeam.role == GameRole.DEFENCE;
		}
		
		public function get isPlayerWinning():Boolean
		{
			return playerTeam.score > opponentTeam.score;
		}
		
		public function get isOpponentWinning():Boolean
		{
			return opponentTeam.score > playerTeam.score;
		}
		
		// -------------------------------------------------------------------------------
		// PRIVATE METHODS
		// -------------------------------------------------------------------------------
		
		// DataObject implementation
		
		override protected function validate(data:Object):void
		{
			if (!data.hasOwnProperty(JSON_PROPERTY_TURN)) throwLoadMissingPropError(JSON_PROPERTY_TURN);
			if (!data.hasOwnProperty(JSON_PROPERTY_HOME)) throwLoadMissingPropError(JSON_PROPERTY_HOME);
			if (!data.hasOwnProperty(JSON_PROPERTY_AWAY)) throwLoadMissingPropError(JSON_PROPERTY_AWAY);
			if (_side == GameSide.NONE && !data.hasOwnProperty(JSON_PROPERTY_SIDE)) throwLoadMissingPropError(JSON_PROPERTY_SIDE);
		}
		
		override protected function fill(data:Object):void
		{
			// Id
			if (data.hasOwnProperty(JSON_PROPERTY_ID))
			{
				_id = uint(data[JSON_PROPERTY_ID]);
			}
			// Turn
			_turn = int(data[JSON_PROPERTY_TURN]);
			// Side
			if (data.hasOwnProperty(JSON_PROPERTY_SIDE))
			{
				_side = GameSide.getByName(data[JSON_PROPERTY_SIDE]);
			}
			// Game team loading
			_home.load(data[JSON_PROPERTY_HOME]);
			_away.load(data[JSON_PROPERTY_AWAY]);
			// Ball loading
			_ball.load(data);
			// Apply the player team flag to the teams
			_home.setIsPlayerTeam(_side == GameSide.HOME);
			_away.setIsPlayerTeam(_side != GameSide.HOME);
			// Raises event EVENT_GAME_CHANGED
			dispatchGameChanged();
		}
		
		// Turns
		
		// Event dispatchers
		
		protected function dispatchGameChanged():void
        {
			dispatchEvent(new GameEvent(GameEvent.EVENT_GAME_CHANGED, this));
        }
		
		protected function dispatchGameStatusChanged(oldStatus:GameStatus, newStatus:GameStatus):void
		{
			dispatchEvent(new GameStatusChangedEvent(this, oldStatus, newStatus));
		}
		
		// -------------------------------------------------------------------------------
		// PUBLIC METHODS
		// -------------------------------------------------------------------------------
		
		/**
		 * Change the game status 
		 * @param newStatus 
		 */		
		public function changeStatus(newStatus:GameStatus):void
		{
			// Old status
			var oldStatus:GameStatus = _status;
			// Apply the new status
			_status = newStatus;
			// DEBUG > Trace status change
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + " STATUS > changet {0} to {1}", oldStatus, newStatus);
			// Dispatch EVENT_GAME_STATUS_CHANGED event
			dispatchGameStatusChanged(oldStatus, newStatus);
		}
		
		/**
		 * Return the list of action the player can choose for PLAY phase
		 * @return  
		 */		
		// public function getActionsForPlayPhase():Vector.<GameAction>
		public function getActionsForPlayPhase():Array
		{
			// Action list
			// var actions:Vector.<GameAction> = new Vector.<GameAction>();
			var actions:Array = new Array();
			// Check if the player is attacking
			if (playerTeam.isAttacking)
			{
				// Chek the allowd actions for the current position
				if (GameAction.PASS.allowed(playerActivePosition)) 		actions.push(GameAction.PASS);
				if (GameAction.DRIBBLE.allowed(playerActivePosition)) 	actions.push(GameAction.DRIBBLE);
				if (GameAction.CROSS.allowed(playerActivePosition)) 	actions.push(GameAction.CROSS);
				if (GameAction.SHOOT.allowed(playerActivePosition)) 	actions.push(GameAction.SHOOT);
			}
			return actions;
		}
		
		/**
		 * Return the list of action the player can choose for PLAY phase
		 * @return  
		 */		
		// public function getActionsForPlayPhase():Vector.<GameAction>
		public function getTargetsForGameAction(action:GameAction):Array
		{
			// Action list
			// var actions:Vector.<GameAction> = new Vector.<GameAction>();
			var positions:Array = new Array();
			// Check if the player is attacking
			if (playerTeam.isAttacking)
			{
				// Shoot action is always to the goalkeeper, no chooises
				if (action != GameAction.SHOOT)
				{
					var avail:Vector.<Position> = Position.getNearbyPositions(playerActivePosition);
					for (var i:int=0; i < avail.length; i++)
					{
						positions.push( avail[i] );
					}
				}
			}
			return positions;
		}
		
		/**
		 * Restituisce la lista di tutte le carte giocabili dal giocatore nella mano corrente 
		 * @return 
		 * 
		 */		
		// public function getCardsToPlay(selected:Vector.<GameActionCard>):Vector.<GameActionCard>
		public function getCardsToPlay(selected:Array):Array
		{
			// Action card list
			var cards:Array = new Array();
			var card:GameActionCard = null;
			// Valid flag
			var valid:Boolean = true;
			
			// Player stamina check
			if (playerActivePlayerCard.canPlayCards)
			{
				// Filter cards by by role
				for (var i:int = 0; i < playerTeam.cards.hand.length; i++)
				{
					// reset validity flag
					valid = true;
					// Card
					card = playerTeam.cards.hand[i];
					// Card school check
					if (valid) valid = card.matchSchools(playerActivePlayerCard.schools);
					// Role check
					if (valid) valid = (card.role == Role.ALL || card.role == Role.NONE || card.role == playerActivePlayerCard.role);
					// Ball condition
					if (valid)	valid =	card.ballCondition == CardBallCondition.ALWAYS ||
										(playerTeam.isAttacking 	&& card.ballCondition == CardBallCondition.WITH_BALL) ||
										(!playerTeam.isAttacking	&& card.ballCondition == CardBallCondition.WITHOUT_BALL);
					
					// LT_TODO: target conditions check
					
					// Not already selected
					if (valid) valid = selected.indexOf(card) == -1;
					
					
					// Adds the card to the output list
					if (valid) cards.push(playerTeam.cards.hand[i]);
				}
			}
			return cards;
		}
		
		/**
		 * Data una carta scelta come input, restituisce la lista di tutte le carte targettabili 
		 * @param card
		 * @return 
		 * 
		 */		
		public function getTargetsActionsForCard(card:GameActionCard):Vector.<GameActionCard>
		{
			return null;
		}
		
		/**
		 * 
		 * @param card
		 * @return  
		 */		
		public function getTargetsPlayersForCard(card:GameActionCard):Vector.<GamePlayerCard>
		{
			return null;
		}
		
		/**
		 * Data una carta scelta come input, restituisce la lista di tutte le carte targettabili 
		 * @param card
		 * @return 
		 * 
		 */		
		public function getTargetsPositionsForCard(action:GameAction):Vector.<Position>
		{
			return null;
		}
		
		/**
		 * Restituisce le carte giocatore disponibili per la sostituzione
		 * @return  
		 */		
		public function getPlayersToSubstitute():Vector.<GamePlayerCard>
		{
			return null;
		}
		
		/**
		 * Updates Game
		 * @return  
		 */		
		public function updateFormations(playerFormation:GameFormation, opponentFormation:GameFormation):void
		{
			playerTeam.formation.update(playerFormation);
			opponentTeam.formation.update(opponentFormation);
		}
		
		/**
		 * Updates the status of the cards for player and opponent, usually called
		 * after a discard phase
		 * @param playerCards Status of the player cards
		 * @param opponentCards Status of the opponent cards
		 */		
		public function updateCards(playerCards:GameCards, opponentCards:GameCards):void
		{
			// Update cards
			playerTeam.cards.update(playerCards);
			opponentTeam.cards.update(opponentCards);
		}
		
		/**
		 * 
		 * @param gameTurn
		 */		
		public function updateTurns(gameTurn:GameTurn):void
		{
			// Add the turn to turn list
			_turns.add(gameTurn);
		}
		
		public function passPhase():void
		{
			_passedPhases++;
		}
				
		public function equals(game:Game):Boolean
		{
			return	_id				== game.id			&&
					_status			== game.status		&&
					_side 			== game.side		&&
					_turn 			== game.turn 		&&
					_passedPhases		== game.passedPhases	&&
					_options.equals(game.options)		&&
					_player.equals(game.player)			&&
					_opponent.equals(game.opponent)		&&
					_deck.equals(game.deck)				&&
					_ball.equals(game.ball)				&&
					playerTeam.equals(game.playerTeam)	&&
					opponentTeam.equals(game.opponentTeam);
			
			// LT_TODO: add GameTurns to the check
			
		}
		
		override public function clear():void
		{
			// Clear data
			_id		= DEFAULT_ID;
			_turn 	= DEFAULT_TURN;
			_side 	= DEFAULT_SIDE;
			_status = DEFAULT_STATUS;
			// Clear objects
			_ball.clear();
			_home.clear();
			_away.clear();
			_turns.clear();
			// Dispatch EVENT_GAME_CHANGED
			dispatchGameChanged();
		}
		
		public function dump(source:Array=null, indentDepth:int=0):Array
		{
			return Dumper.dump(this, null, source, indentDepth);
		}
		
	}

}